package com.capgemini.orf.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public final class TokenManager {
	private static final Logger log = Logger.getLogger(TokenManager.class
			.getName());

	private final static int EXPIRATION_DURATION = 10 * 60 * 60 * 1000;
	private final static int EXPIRATION_LENGTH = Long.SIZE / 8;

	private final static byte[] ivBytes = "1357924685376713".getBytes();
	private final static SecretKey sKey = generateKey();

	private final static SecretKey generateKey() {
		try {
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			kgen.init(128);
			return kgen.generateKey();
		} catch (NoSuchAlgorithmException e) {
			log.log(Level.SEVERE, "Secretkey-algoritm not supported...");
		}

		return null;
	}

	public final static String createTokenFor(String email,
			String hashedPassword) throws AuthenticationException {
		try {
			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, sKey, new IvParameterSpec(ivBytes));

			Calendar calendar = Calendar.getInstance(
					TimeZone.getTimeZone("GMT"), new Locale("en"));
			long expirationTime = calendar.getTimeInMillis()
					+ EXPIRATION_DURATION;
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			DataOutputStream dos = new DataOutputStream(bos);
			dos.writeLong(expirationTime);
			dos.flush();
			byte[] expirationBytes = bos.toByteArray();
			byte[] emailBytes = email.getBytes();
			byte[] hashedPasswordBytes = hashedPassword.getBytes();
			byte[] bytes = new byte[emailBytes.length
					+ hashedPasswordBytes.length + EXPIRATION_LENGTH];

			System.arraycopy(expirationBytes, 0, bytes, 0, EXPIRATION_LENGTH);
			System.arraycopy(emailBytes, 0, bytes, EXPIRATION_LENGTH,
					emailBytes.length);
			System.arraycopy(hashedPasswordBytes, 0, bytes, EXPIRATION_LENGTH
					+ emailBytes.length, hashedPasswordBytes.length);

			return asHex(cipher.doFinal(bytes));
		} catch (GeneralSecurityException e) {
			throw new AuthenticationException(
					"An error occurred while authenticating: " + e.getMessage());
		} catch (IOException e) {
			throw new AuthenticationException(
					"An error occurred while authenticating: " + e.getMessage());
		}
	}

	private static String asHex(byte buf[]) {
		StringBuffer strbuf = new StringBuffer(buf.length * 2);
		int i;

		for (i = 0; i < buf.length; i++) {
			if (((int) buf[i] & 0xff) < 0x10)
				strbuf.append("0");

			strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
		}

		return strbuf.toString();
	}

	public final static boolean validateToken(String token)
			throws AuthenticationException {
		try {
			byte[] bytes = new BigInteger(token, 16).toByteArray();
			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(ivBytes));
			byte[] decrypted = cipher.doFinal(bytes);

			if (decrypted.length >= EXPIRATION_LENGTH) {
				Calendar calendar = Calendar.getInstance(
						TimeZone.getTimeZone("GMT"), new Locale("en"));
				long now = calendar.getTimeInMillis();
				ByteArrayInputStream bis = new ByteArrayInputStream(decrypted);
				DataInputStream dis = new DataInputStream(bis);
				long expirationTime = dis.readLong();

				long remainingTime = expirationTime - now;
				if (remainingTime >= 0 && remainingTime <= EXPIRATION_DURATION) {
					return true;
				}
			}

			return false;
		} catch (GeneralSecurityException e) {
			throw new AuthenticationException(
					"An error occurred while authenticating: " + e.getMessage());
		} catch (IOException e) {
			throw new AuthenticationException(
					"An error occurred while authenticating: " + e.getMessage());
		}
	}
}
